﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using log4net;
using log4net.Config;

namespace BrokenWSDLChecker.Lib
{
    public static class ObjectPopulator
    {
        private static readonly ILog _log = LogManager.GetLogger(typeof(ObjectPopulator));

        static ObjectPopulator()
        {
            XmlConfigurator.Configure();
        }

        internal static bool IsNullable<T>(T obj)
        {
            if (!typeof(T).IsGenericType)
                return false;

            return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
        }

        /// <summary>
        /// Creates an object With non default values.
        /// 1)  Use CreateFilledObject to create an object with all values set
        /// 2)  On the other end of the wire call WasSerializedProperly to confirm all values flowed through the call correctly.
        /// </summary>
        /// <typeparam name="T">Type of object. Only supports typical DTO style classes.</typeparam>
        /// <returns>Object with all public properties filled, array's, list's etc.</returns>
        public static T CreateFilledObject<T>()
        {
            return (T)CreateFilledObject2(typeof(T));
        }

        internal static object CreateFilledObject2(Type t)
        {
            bool isNullable = t.IsGenericType ? t.GetGenericTypeDefinition() == typeof(Nullable<>) : false;

            if (isNullable)
            {
                var type = Nullable.GetUnderlyingType(t);
                switch (Type.GetTypeCode(type))
                {
                    case TypeCode.Boolean:
                        return true;
                    case TypeCode.Int32:
                        return 1234;
                    case TypeCode.DateTime:
                        return new DateTime(2001, 01, 01, 01, 01, 01);
                    case TypeCode.Double:
                        return 923.13824;
                    case TypeCode.Byte:
                        return 23;
                    case TypeCode.Char:
                        return 'e';
                    case TypeCode.Decimal:
                        return 9823.898m;
                }
            }

            var obj = Activator.CreateInstance(t);
            var properties = t.GetProperties();
            foreach (var propertyInfo in properties)
            {
                switch (Type.GetTypeCode(propertyInfo.PropertyType))
                {
                    case TypeCode.Byte:
                        propertyInfo.SetValue(obj, 23, null);
                        break;
                    case TypeCode.Decimal:
                        propertyInfo.SetValue(obj, 9823.898m, null);
                        break;
                    case TypeCode.Char:
                        propertyInfo.SetValue(obj, 'e', null);
                        break;
                    case TypeCode.Boolean:
                        propertyInfo.SetValue(obj, true, null);
                        break;
                    case TypeCode.Int32:
                        propertyInfo.SetValue(obj, 1234, null);
                        break;
                    case TypeCode.String:
                        propertyInfo.SetValue(obj, "test", null);
                        break;
                    case TypeCode.DateTime:
                        propertyInfo.SetValue(obj, new DateTime(2001, 01, 01, 01, 01, 01), null);
                        break;
                    case TypeCode.Double:
                        propertyInfo.SetValue(obj, 923.13824, null);
                        break;
                    default:
                        _log.DebugFormat("Attempting to fill {0} of type {1}", propertyInfo.Name, propertyInfo.PropertyType);
                        if (typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType))
                        {
                            var propertyTypeIfList = propertyInfo.PropertyType.GetGenericArguments();
                            if (propertyTypeIfList != null && propertyTypeIfList.Count() > 0)
                            {
                                var filledElement = CreateFilledObject2(propertyTypeIfList[0]);
                                var createdArray = Array.CreateInstance(propertyTypeIfList[0], 1);
                                createdArray.SetValue(filledElement, 0);
                                //var createdList = typeof(List<>).MakeGenericType(new Type[] { propertyTypeIfList[0] });
                                //var list = Activator.CreateInstance(createdList);
                                //var castList = (IList) list;
                                //castList.Add(filledElement);
                                propertyInfo.SetValue(obj, createdArray, null);
                            }
                            else
                            {
                                var elementType = propertyInfo.PropertyType.GetElementType();
                                var filledElement = CreateFilledObject2(elementType);
                                var createdArray = Array.CreateInstance(elementType, 1);
                                createdArray.SetValue(filledElement, 0);
                                propertyInfo.SetValue(obj, createdArray, null);
                            }

                        }
                        else
                        {
                            propertyInfo.SetValue(obj, CreateFilledObject2(propertyInfo.PropertyType), null);
                        }
                        break;
                }
            }
            return obj;
        }

        internal static bool WasSerializedProperly3(this object obj)
        {
            return HasAllNonDefaultValues(obj);
        }

        internal static object CreateGeneric(Type generic, Type innerType, params object[] args)
        {
            System.Type specificType = generic.MakeGenericType(new System.Type[] { innerType });
            return Activator.CreateInstance(specificType, args);
        }

        internal static bool MatchesFilledType<T>(T param)
        {
            var filled = (T)CreateFilledObject2(typeof(T));
            return GenericObjectsMatch(param, filled);
        }

        internal static class ValueTypeHelper
        {
            public static bool IsNullable<T>(T t) { return false; }
            public static bool IsNullable<T>(T? t) where T : struct { return true; }
        }

        /// <summary>
        /// Call on object that was created with CreateFilledObject.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>Boolean value indicating whether they are equal</returns>
        public static bool WasSerializedProperly(this object obj)
        {
            var filled = CreateFilledObject2(obj.GetType());
            return ObjectsAreMatch(obj, filled);
        }

        internal static bool ObjectsAreMatch<T>(T obj, T obj2)
        {
            var jsSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
            return jsSerializer.Serialize(obj) == jsSerializer.Serialize(obj2);
        }

        internal static bool HasAllNonDefaultValues<T>(T obj)
        {
            bool nothingIsNull = true;
            if (!obj.GetType().IsClass)
            {
                return !Activator.CreateInstance(obj.GetType()).Equals(obj);    
            }
            else
            {
                switch (Type.GetTypeCode(obj.GetType()))
                {
                    case TypeCode.String:
                        return !EqualityComparer<T>.Default.Equals(obj, default(T));
                    default:
                        if (!typeof(IEnumerable).IsAssignableFrom(obj.GetType()))
                        {
                            var properties = obj.GetType().GetProperties();
                            while (properties.Count() < 1)
                            {
                                properties = obj.GetType().GetProperties();
                            }
                            if (properties.Count() < 1)
                            {
                                properties = obj.GetType().GetProperties();
                                throw new Exception("Couldn't find Properties");
                            }
                            foreach (var propertyInfo in properties)
                            {
                                if (typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType))
                                {
                                    if (propertyInfo.GetValue(obj, null) == null)
                                    {
                                        return false;
                                    }

                                    var enumerator = (propertyInfo.GetValue(obj, null) as IEnumerable).GetEnumerator();
                                    if (!enumerator.MoveNext())
                                    {
                                        return false;
                                    }

                                    foreach (var element in propertyInfo.GetValue(obj, null) as IEnumerable)
                                    {
                                        nothingIsNull = nothingIsNull && HasAllNonDefaultValues(element);
                                        if (!nothingIsNull)
                                        {
                                            return false;
                                        }
                                    }
                                }
                                else
                                {
                                    nothingIsNull = nothingIsNull && HasAllNonDefaultValues(propertyInfo.GetValue(obj, null));
                                    if (!nothingIsNull)
                                    {
                                        return false;
                                    }
                                }
                            }
                        }
                        else
                        {
                            if (obj == null)
                            {
                                return false;
                            }

                            var enumerator = (obj as IEnumerable).GetEnumerator();
                            if (!enumerator.MoveNext())
                            {
                                return false;
                            }

                            foreach (var element in obj as IEnumerable)
                            {
                                nothingIsNull = nothingIsNull && HasAllNonDefaultValues(element);
                                if (!nothingIsNull)
                                {
                                    return false;
                                }
                            }
                        }
                        
                        break;
                }
            }
                
            return nothingIsNull;
            
        }

        private static bool GenericObjectsMatch<T>(T first, T second)
        {
            bool matches = true;
            if (first == null && second != null || second == null && first != null)
            {
                return false;
            }
            if (!first.GetType().IsClass)
            {
                var equals =  first.Equals(second);
                return equals;
            }
            if (typeof(IEnumerable).IsAssignableFrom(first.GetType()))
            {
                var enumeratorFirst = (first as IEnumerable).GetEnumerator();
                var enumeratorSecond = (second as IEnumerable).GetEnumerator();
                var firstMoved = enumeratorFirst.MoveNext();
                var secondMoved = enumeratorSecond.MoveNext();

                while (firstMoved && secondMoved)
                {
                    matches = matches && GenericObjectsMatch(enumeratorFirst.Current, enumeratorSecond.Current);
                    if (!matches)
                    {
                        return false;
                    }
                    firstMoved = enumeratorFirst.MoveNext();
                    secondMoved = enumeratorSecond.MoveNext();
                }

                return (firstMoved == secondMoved);
            }
            var properties = first.GetType().GetProperties();
            bool isSame = true;
            foreach (var propertyInfo in properties)
            {
                switch (Type.GetTypeCode(propertyInfo.PropertyType))
                {
                    case TypeCode.Boolean:
                    case TypeCode.Int32:
                    case TypeCode.String:
                    case TypeCode.DateTime:
                    case TypeCode.Double:
                        _log.DebugFormat("Comparing {0} of type {1} with values {2}, {3}", propertyInfo.Name, propertyInfo.PropertyType, propertyInfo.GetValue(first, null), propertyInfo.GetValue(second, null));
                        isSame = isSame && GenericObjectsMatch(propertyInfo.GetValue(first, null), propertyInfo.GetValue(second, null));
                        break;
                    default:
                        isSame = isSame &&
                                 GenericObjectsMatch(propertyInfo.GetValue(first, null),
                                                     propertyInfo.GetValue(second, null));
                        break;
                }
            }
            return isSame;
        }
    }
}
